import hmac
import json
import requests
import urllib.parse as urlparse
import hashlib
import sys
import time

from coin.exchange.base.kr_rest.auth_base import AuthKey
from coin2.api_client.common import (SyncQueryer)
from enum import Enum

class Side(Enum):
  BUY = "BUY"
  SELL = "SELL"

class Type(Enum):
  LIMIT = "LIMIT"
  MARKET = "MARKET"
  STOP_LOSS = "STOP_LOSS"
  STOP_LIMIT = "STOP_LIMIT"
  TAKE_PROFIT = "TAKE_PROFIT"
  TAKE_PROFIT_LIMIT = "TAKE_PROFIT_LIMIT"

class TimeInForce(Enum):
  GOOD_TILL_CANCEL = "GOOD_TILL_CANCEL"
  FILL_OR_KILL = "FILL_OR_KILL"
  IMMEDIATE_OR_CANCEL = "IMMEDIATE_OR_CANCEL"

class CryptoAuth(requests.auth.AuthBase):
  def __init__(self, key):
    self._key = AuthKey.from_file(key)

  def _gen_signature(self, method: str, id: str, api_key: str, nonce: str, param_str: str = None):
    assert method, "method must be not None and not empty!"
    assert id, "id must be not None and not empty!"
    assert api_key, "api_key must be not None and not empty!"
    assert nonce, "nonce must be not None and not empty!"

    before_signature = ''.join(filter(None, [method, id, api_key, param_str, nonce]))

    signature = hmac.new(self._key.secret_key_bytes, before_signature.encode(), digestmod=hashlib.sha256)
    return signature.hexdigest()

  # First ensure the params are alphabetically sorted by key
  # Copy from https://exchange-docs.crypto.com/spot/index.html?python#digital-signature
  def params_to_str(self, obj, level):
      return_str = ""
      for key in sorted(obj):
          return_str += key
          if obj[key] is None:
              return_str += 'null'
          elif isinstance(obj[key], list):
              for subObj in obj[key]:
                  return_str += self.params_to_str(subObj, ++level)
          else:
              return_str += str(obj[key])
      return return_str

  def __call__(self, req: requests.PreparedRequest):
    nonce = str(int(time.time() * 1000))
    method = urlparse.urlparse(req.url).path[4:]
    params = json.loads(req.body)
    param_str = self.params_to_str(params, 0)
    signature = self._gen_signature(method, nonce, self._key.access_key, nonce, param_str)

    body = {
      'id': nonce,
      'params': params,
      'method': method,
      'api_key': self._key.access_key,
      'nonce': nonce,
      'sig': signature,
    }

    req.prepare_body(None, None, json=body)

    # print(req.headers)
    # print(req.body)
    # print(req.url)

    return req

class CryptoPrivateClient():
  def __init__(self, key_path: str, ip=None):
    self._auth = CryptoAuth(key_path)
    self._url = "https://api.crypto.com"

    self._query = SyncQueryer(api_host=self._url, auth=self._auth, ip=ip)

  def get_open_order(self, instrument_name: str = None, page_size: int = None, page: int = None):
    method = "POST"
    json = {
      'instrument_name': instrument_name,
      'page_size': page_size,
      'page': page,
    }
    path = "/v2/private/get-open-orders"

    response = self._query.query(method=method, path=path, json=json)
    return response.json()

  def cancel_order(self, instrument_name: str, order_id: str):
    assert instrument_name, "instrument_name must be not None and not empty!"
    assert order_id, "order_id must be not None and not empty!"

    method = "POST"
    json = {
      'instrument_name': instrument_name,
      'order_id': order_id,
    }
    path = "/v2/private/cancel-order"

    response = self._query.query(method=method, path=path, json=json)
    return response.json()

  def submit_order(
    self,
    instrument_name: str,
    side: Side,
    type: Type,
    price: str = None,
    quantity: str = None,
    notional: str = None,
    client_oid: str = None,
    time_in_force: TimeInForce = None,
    trigger_price: str = None,
  ):
    assert instrument_name, "instrument_name must be not None and not empty!"
    assert side, "side must be not None!"
    assert type, "type must be not None!"

    method = "POST"
    json = {
      'instrument_name': instrument_name,
      'side': side.value,
      'type': type.value,
      'price': price,
      'quantity': quantity,
      'notional': notional,
      'client_oid': client_oid,
      'time_in_force': time_in_force and time_in_force.value,
      'trigger_price': trigger_price,
    }
    path = "/v2/private/create-order"

    response = self._query.query(method=method, path=path, json=json)
    return response.json()

# api doc url
# https://exchange-docs.crypto.com/spot/index.html#introduction

if __name__ == "__main__":
  key_path = sys.argv[1]
  
  crypto = CryptoPrivateClient(key_path)
  info = crypto.get_open_order()
  # info = crypto.cancel_order("BTC_USDT", "12345667")
  # info = crypto.submit_order("BTC_USDT", Side.SELL, Type.LIMIT, "100000000", "0.00000001")
  print(json.dumps(info, indent=2))
